<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        .virtual-box {
            width: 200px;
            height: 300px;
            margin: 20px auto;
            overflow: auto;
            border: 1px solid #ccc;
            position: relative;
        }
        .virtual-scrollbar {
            position: absolute;
            width: 100%;
            height: 100%;
        }
        /* 滚动列表容器高度“一定要添加”， 否则鼠标拖动滚动条到底部时列表会发生闪动问题 */
         .virtual-list {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
         }
        .virtual-item {
            height: 50px;
            line-height: 50px;
            padding: 5px 10px;
        }
    </style>
</head>

<body>
    <div class="virtual-box">
        <div class="virtual-scrollbar"></div>
        <div class="virtual-list">
            <div class="virtual-item"></div>
          <!-- item-1 -->
          <!-- item-2 -->
          <!-- ...... -->
          <!-- item-n -->
        </div>
    </div>
    <script>
        /**
         * 实现思路：
         * 1、设置列表项固定高度 itemHeight
         * 2、计算并设置虚拟滚动条高度： 列表项数量*itemHeight
         * 3、计算一屏展示的数量：viewCount = 容器高度/列表项高度
         * 4、设置渲染内容的开始和结束位置
         *  startIndex=0 渲染内容对应在数据中索引初始值
         *  endIndex= viewCount 渲染内容对应在数组中索引结束值
         * 5、封装函数实现渲染数据 render
         *      渲染的数据从starIndex开始到endIndex结束
         * 6、监听滚动事件
         *  6-1、计算滚动屏幕外的内容有多少个列表项，即重新计算滚动后
         *      渲染数据索引的开始索引值
         *          startIndex = this.scrollTop/itemHeight
         *  6-2、计算滚动后的结束索引值
         *          endIndex = viewCount + startIndex
         *  6-3、重新渲染startIndex到endIndex的数据
         *  6-4、重新渲染后，需要把列表容器的位置重新位移到顶部
         *      偏移量的计算
         *      startOffset = this.scrollTop - (this.scrollTop%itemHeight)
         *     通过transform属性设置列表容器位置
         * 
         * */ 
        let arr = []
        for(let i=1;i<=1000;i++){
            arr.push("item-"+i)
        }
        // 获取需要用到的容器
        const container = document.querySelector(".virtual-box")
        const scrollBar = document.querySelector(".virtual-scrollbar")
        const renderBox = document.querySelector(".virtual-list")


        // 列表项高度
        const itemHeight = 50;
        // 设置滚动条高度
        scrollBar.style.height = arr.length*itemHeight+"px"
        // 一屏展示的数量
        const viewCount = Math.ceil(container.offsetHeight/itemHeight)
        // 设置展示数据在数组中的开始和结束位置
        let startIndex = 0;
        let endIndex = viewCount;

        
        // 渲染数据
        function render(startIndex, endIndex){
            // renderBox.innerHTML = ""
            // 判断数据已经展示完毕
            if(endIndex>arr.length){
                endIndex = arr.length
            }
            let divStr = ""
            for(let i = startIndex; i<endIndex;i++){
                divStr+=`<div class="virtual-item">${arr[i]}</div>`
            }
            
            renderBox.innerHTML = divStr
        }
        render(startIndex, endIndex)

        // 监听滚动
        container.addEventListener("scroll", function(){
            // 计算当前移动了多少个列表项 = 滚动的高度/列表项的高度
            startIndex = Math.floor(this.scrollTop/itemHeight)
            endIndex = viewCount + startIndex
            render(startIndex, endIndex)
            // 滚动后 list容器位置会发生偏移，需要把容器移动到原来的位置
            let startOffset = this.scrollTop - (this.scrollTop % itemHeight)
            renderBox.style.transform = `translateY(${startOffset}px)`
                
        })
        
        
    </script>
</body>

</html>